"""
Terminal for serial port

Requirement:

    + pyserial
    + colorama
    + py-getch
    + click
    + xmodem

topology:
  stdin ---------> queue ---------> serial.write
                   serial.read ---> stdout
  |--read_input thread--|
                        |----main thread----|
"""

import os
if os.name == 'nt':
    os.system('title Terminal S')

from collections import deque
import sys
import threading

import colorama
import click
import serial
from xmodem import XMODEM
from serial.tools import list_ports
from .serial_ports import serial_ports


xmodem_sendfile = None # last file being sent

def run(port, baudrate, parity='N', stopbits=1):
    global xmodem_sendfile
    run.mode = 'r' # r - running, x - xmodem, q - quit, d - disconnect
    try:
        device = serial.Serial(port=port,
                                baudrate=baudrate,
                                bytesize=8,
                                parity=parity,
                                stopbits=stopbits,
                                timeout=0.1)
    except:
        print('--- Failed to open {} ---'.format(port))
        return 0

    print('--- {} is connected. Press Ctrl+] to quit, Ctrl+l to clear screen. ---'.format(port))
    print('--- Press Ctrl+X or N to send xmodem file ---')
    queue = deque()

    def xmodem_putc(data, timeout=1):
        return device.write(data) or None  # note that this ignores the timeout

    def xmodem_getc(size, timeout=1):
        return device.read(size) or None

    def screen_clear():
        if os.name == 'nt':
            os.system('cls')# for windows
        else:
            os.system('clear')# for mac and linux(here, os.name is 'posix')

    def read_input():
        global xmodem_sendfile
        if os.name == 'nt':
            from msvcrt import getch
        else:
            import tty
            import termios
            stdin_fd = sys.stdin.fileno()
            tty_attr = termios.tcgetattr(stdin_fd)
            tty.setraw(stdin_fd)
            getch = lambda: sys.stdin.read(1).encode()

        while device.is_open:
            ch = getch()
            # print(ch)
            if ch == b'\x1d':         # 'ctrl + ]' to quit
                run.mode = 'q'
                break
            elif ch == b'\x18':       # 'ctrl + x' to send last file by xmodem
                run.mode = 'x'
                break
            elif ch == b'\x0e':       # 'ctrl + n' to send new file by xmodem
                run.mode = 'x'
                xmodem_sendfile = None 
                break
            elif ch == b'\x0c':       # 'ctrl +l' to clear screen
                screen_clear()
                continue;
            if ch == b'\x00' or ch == b'\xe0':  # arrow keys' escape sequences
                ch2 = getch()
                esc_dict = { b'H': b'A', b'P': b'B', b'M': b'C', b'K': b'D', b'G': b'H', b'O': b'F' }
                if ch2 in esc_dict:
                    queue.append(b'\x1b[' + esc_dict[ch2])
                else:
                    queue.append(ch + ch2)
            else:  
                queue.append(ch)

        if os.name != 'nt':
            termios.tcsetattr(stdin_fd, termios.TCSADRAIN, tty_attr)

    colorama.init()

    thread = threading.Thread(target=read_input)
    thread.start()
    while thread.is_alive():
        try:
            length = len(queue)
            if length > 0:
                device.write(b''.join(queue.popleft() for _ in range(length)))

            line = device.readline()
            if line:
                print(line.decode(errors='replace'), end='', flush=True)
        except IOError:
            print('--- {} is disconnected ---'.format(port))
            run.mode = 'd' 
            break

    def s(total_packets, success_count, error_count):
        if not hasattr(s, 'err'): s.err = 0
        if(success_count)&0x1F == 0x1F: print('.',end='') # print . every 32 packets
        if(s.err != error_count): 
            print('e', end='')
            s.err = error_count

    if(run.mode == 'x'): # xmodem mode
        while True:
            if xmodem_sendfile is None:
                xmodem_sendfile = input("File path('q' to skip): ").strip().strip('\"')
                if xmodem_sendfile == 'q': 
                    xmodem_sendfile = None
                    break
            try:
                stream = open(xmodem_sendfile, 'rb')
                print("Xmodem send:", xmodem_sendfile)
                device.timeout = 1;
                modem = XMODEM(xmodem_getc, xmodem_putc)
                print(["Fail!","Success!"][modem.send(stream,callback=s,retry=32)])
                device.close()
                break
            except Exception as e: 
                print(e)
                xmodem_sendfile = None
        return 1

    device.close()
    if thread.is_alive():
        print('--- Press R to reconnect the device, or press Enter to exit ---')
        thread.join()
        if queue and queue[0] in (b'r', b'R'):
            return 1
    return 0


CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])

@click.command(context_settings=CONTEXT_SETTINGS)
@click.option('-p', '--port', default=None, help='serial port name')
@click.option('-b', '--baudrate', default=115200, help='set baud reate')
@click.option('--parity', default='N', type=click.Choice(['N', 'E', 'O', 'S', 'M']), help='set parity')
@click.option('-s', '--stopbits', default=1, help='set stop bits')
@click.option('-l', is_flag=True, help='list serial ports')
def main(port, baudrate, parity, stopbits, l):
    if port is None:
        ports = serial_ports() #list_ports.comports()
        if not ports:
            print('--- No serial port available ---')
            return
        if len(ports) == 1:
            port = ports[0] #ports[0][0]
        else:
            print('--- Available Ports ----')
            for i, v in enumerate(ports):
                # print('---  {}: {} {}'.format(i, v[0], v[2]))
                print('---  {}: {}'.format(i, v))
            if l:
                return
            raw = input('--- Select port index: ')
            try:
                n = int(raw)
                port = ports[n] #ports[n][0]
            except:
                return

    if os.name == 'nt':
        os.system('title {}-{}'.format(port, baudrate)) #set title for windows

    while run(port, baudrate, parity, stopbits):
        pass

if __name__ == "__main__":
    main()
